﻿#include "LED.h"
#include <Windows.h>


/// <summary>
/// 采血间文字
/// </summary>
static char* 采血间号[500] =
{
		"采血①" ,"采血②" ,"采血③" ,"采血④" ,"采血⑤" ,
		"采血⑥" ,"采血⑦" ,"采血⑧" ,"采血⑨" ,"采血⑩"
};


/// <summary>
/// 子线程：查询数据表，实时重绘LED窗口
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
DWORD WINAPI SubThread(LPVOID param)
{
	LED* LEDform = (LED*)param;
	Ui::SettingFormClass* ui = g_MainForm->GetUi();

	//
	// tts
	//
	QTextToSpeech* tts = new QTextToSpeech(LEDform);	//创建tts对象
	tts->setRate(g_MainForm->config->Rate);					//设置语速
	tts->setVolume(g_MainForm->config->Volume);				//设置音量（0.0~1.0）


	// 尝试从配置文件中选取指定的语音包
	QVector<QVoice> voiceList = tts->availableVoices();	//获取可用语音包列表
	if (QString::compare(g_MainForm->config->Voice, "initVoice") != 0)
	{
		for each (QVoice voi in voiceList)
		{
			if (QString::compare(voi.name(), g_MainForm->config->Voice) == 0)
				tts->setVoice(voi);
			break;
		}
	}

	// 前台UI展示当前语音包、可用语音包
	ui->cb_voice->clear();
	ui->cb_voice->addItem(tts->voice().name());
	ui->cb_voice->setCurrentIndex(0);
	for each (QVoice voi in voiceList)
	{
		if (QString::compare(voi.name(), tts->voice().name()) != 0)
			ui->cb_voice->addItem(voi.name());
	}

	// log
	g_MainForm->logger->Logging(
		QString().fromLocal8Bit("TTS初始化：可用语音包数=%1，当前语音包=%2，语速=%3，音量=%4 ")
		.arg(voiceList.size())
		.arg(tts->voice().name())
		.arg(tts->rate())
		.arg(tts->volume())
	);

	// 连接数据库
	LEDform->Connect();

	while (true)
	{
		//g_MainForm->logger->Logging(QString::fromLocal8Bit("轮询一次... "));

		// 查询：今日尚未叫号的人数
		QSqlQuery queryRemain = QSqlQuery(LEDform->db);
		queryRemain.prepare(QString("SELECT count(1) FROM fzt_pd_dlb WHERE rdsj>=trunc(sysdate,'dd') AND pdzt=0 AND yqbs = %1").arg(g_MainForm->config->hospitalFlag));
		queryRemain.exec();
		if (queryRemain.next())
			LEDform->remainPeople = queryRemain.value(0).toInt();

		// 查询：今日正在叫号的病人
		QString sqlStr = QString().sprintf("SELECT pdry,jhsj,pdck,pdhm,rdid FROM fzt_pd_dlb t WHERE pdzt=1 AND jhsj>=trunc(sysdate,'dd') AND yqbs = %d", g_MainForm->config->hospitalFlag);
		QSqlQuery query = QSqlQuery(LEDform->db);
		query.prepare(sqlStr);
		query.exec();

		// 重置：为了检查同一个窗口是否同时存在多个pdzt=1的病人
		for (int i = 0; i < g_MainForm->config->LEDcol * g_MainForm->config->LEDrow; i++) {
			g_MainForm->info[i].轮询一次时排队状态是1的病人数 = 0;
		}

		//
		QString voice;
		while (query.next()) //依次取出查询结果的每一条记录
		{
			// 取数据
			QString brxm = query.value(0).toString();
			QDateTime jhsj = query.value(1).toDateTime();
			int pdck = query.value(2).toInt();
			QString pdhm = query.value(3).toString();
			int rdid = query.value(4).toInt();

			// 窗口号小于1时跳过本次叫号，在PB端应设置正确的窗口号
			if (pdck == NULL || pdck < 1) { g_MainForm->logger->Logging(QString::fromLocal8Bit("【错误】已跳过本次叫号，因为窗口号不能为0 ")); continue; }

			// log
			QString oldBrxm = g_MainForm->info[pdck - 1].姓名;
			QString oldJhsj = g_MainForm->info[pdck - 1].叫号时间.toString("yyyy-MM-dd HH:mm:ss");

			//
			// 如果同一个窗口是否同时存在多个pdzt=1的病人，则把叫号时间更早的病人的pdzt改为2
			//
			g_MainForm->info[pdck - 1].轮询一次时排队状态是1的病人数++;
			int 时间差 = jhsj.toTime_t() - g_MainForm->info[pdck - 1].叫号时间.toTime_t();
			if (g_MainForm->info[pdck - 1].轮询一次时排队状态是1的病人数 >= 2) {
				QSqlQuery modify = QSqlQuery(LEDform->db);
				// log
				g_MainForm->logger->Logging(QString::fromLocal8Bit("【错误】窗口%1出现了第%2个同时被叫号的患者，排队号码=%3，姓名=%4，入队ID=%5，叫号时间=%6，此窗口的上次有效叫号时间=%7")
					.arg(pdck)
					.arg(g_MainForm->info[pdck - 1].轮询一次时排队状态是1的病人数)
					.arg(pdhm)
					.arg(brxm)
					.arg(rdid)
					.arg(jhsj.toString("yyyy-MM-dd HH:mm:ss"))
					.arg(g_MainForm->info[pdck - 1].叫号时间.toString("yyyy-MM-dd HH:mm:ss"))
				);

				if (时间差 > 0) {
					modify.prepare(QString("UPDATE fzt_pd_dlb SET pdzt=2 WHERE rdid=%1").arg(g_MainForm->info[pdck - 1].rdid));
					modify.exec();
					g_MainForm->logger->Logging(QString().fromLocal8Bit("舍去：brxm=%1,jhsj=%2").arg(g_MainForm->info[pdck - 1].姓名).arg(g_MainForm->info[pdck - 1].叫号时间.toString("yyyy-MM-dd HH:mm:ss")));
				}
				else {
					modify.prepare(QString("UPDATE fzt_pd_dlb SET pdzt=2 WHERE rdid=%1").arg(rdid));
					modify.exec();
					g_MainForm->logger->Logging(QString().fromLocal8Bit("舍去：brxm=%1,jhsj=%2").arg(brxm).arg(jhsj.toString("yyyy-MM-dd HH:mm:ss")));

					continue;	// 如果本次的叫号时间更早，则跳过本次叫号

				}
			}

			/*
				开始LED上屏

				原先思路：
					若某窗口点了重叫（本次叫号时间晚于上次叫号时间），则更新
					若某窗口第一次叫号（上次叫号时间是空的），则更新
				现在思路：
					因为PB端后一次的叫号时间会莫名其妙的早于前一次的叫号时间，所以更改为只要叫号时间不同就更新语音、更新文字
			*/
			if ((false == g_MainForm->info[pdck - 1].叫号时间.isNull() && 时间差 != 0)	// 叫号时间有变化，更新
				|| true == g_MainForm->info[pdck - 1].叫号时间.isNull()					// 窗口第一次叫号，更新
				|| rdid != g_MainForm->info[pdck - 1].rdid								// 入队ID不同，更新
				)
			{

				// LED姓名限制长度（字数太长，容易超出LED格子）
				QByteArray qbaBrxm = brxm.toLocal8Bit();
				char srtBrxm_limitLen[50]; memset(srtBrxm_limitLen, 0, sizeof(srtBrxm_limitLen));
				strcpy(srtBrxm_limitLen, qbaBrxm.data());
				srtBrxm_limitLen[(g_MainForm->config->maxCHNword * 2)] = '\0'; // 限制长度

				// LED姓名脱敏（脱敏第二个汉字，每个汉字占2字节）
				char encodeName[50]; memset(encodeName, 0, sizeof(encodeName));
				strcpy(encodeName, srtBrxm_limitLen);
				encodeName[2] = '*';	// 改为星号
				strcpy(&encodeName[3], &srtBrxm_limitLen[4]);

				// 更新此窗口的本次叫号数据
				g_MainForm->info[pdck - 1].叫号时间 = jhsj;
				g_MainForm->info[pdck - 1].姓名 = brxm;
				g_MainForm->info[pdck - 1].加密姓名 = QString::fromLocal8Bit(encodeName);
				g_MainForm->info[pdck - 1].排队号码 = pdhm;
				g_MainForm->info[pdck - 1].rdid = rdid;

				// TTS发出声音
				voice = QString().fromLocal8Bit("请%1号%2到%3号窗口").arg(pdhm).arg(brxm).arg(pdck);
				tts->say(voice);

				// log
				QString logMsg = QString().fromLocal8Bit("[窗口%1] 语音：%2，时间差=%3 ，上次姓名=%4,上次叫号时间=%5,本次姓名=%6,本次叫号时间=%7")
					.arg(pdck)
					.arg(voice)
					.arg(时间差)
					.arg(oldBrxm)
					.arg(oldJhsj)
					.arg(brxm)
					.arg(jhsj.toString("yyyy-MM-dd HH:mm:ss"));
				g_MainForm->logger->Logging(logMsg);
			}

		}

		//立即重绘窗口
		LEDform->ui.centralWidget->update();
		Sleep(1000);
	}

	return 0;
};

/// <summary>
/// 构造器
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
LED::LED(QWidget* parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);


	//初始化右键菜单
	InitRightMenu();

	//
	if (g_MainForm->config->DEBUG == 1)
	{
		//创建控制台
		AllocConsole();
		//将系统的标准输出设备【stdout】重定向到当前打开的控制台
		freopen("conout$", "w", stdout);
		printf("\n\t\t\t--DEBUG MODE--\n");
	}

	//子线程：查询数据库，循环重绘窗口
	this->Thread = CreateThread(NULL, 0, SubThread, this, 0, 0);

}


/// <summary>
/// 设置右键菜单
/// </summary>
static QPoint rightMenuPos;
void LED::InitRightMenu()
{
	// 创建右键菜单
	ui.centralWidget->setContextMenuPolicy(Qt::CustomContextMenu); // 给主窗口设置上下文菜单策略
	QMenu* menu = new QMenu(this);
	QAction* action1 = new QAction(QString::fromLocal8Bit("设置温馨提示"), this);		//菜单动作项
	QAction* action3 = new QAction(QString::fromLocal8Bit("退出"), this);
	menu->addAction(action1);
	menu->addAction(action3);

	// 点击"温馨提示"的槽函数
	connect(action1, &QAction::triggered, [=]()
		{
			g_MainForm->ShowPromptForm(rightMenuPos);
		});
	// 点击"退出"的槽函数
	connect(action3, &QAction::triggered, [=]()
		{
			QApplication* app;
			app->exit(0);
		});
	// 右键菜单的槽函数
	connect(ui.centralWidget, &QLabel::customContextMenuRequested, [=](const QPoint& pos)
		{
			rightMenuPos = pos;			// 参数pos用来传递右键点击时的鼠标的坐标，这个坐标一般是相对于控件左上角而言的
			menu->exec(QCursor::pos());
		});

	// QSS
	menu->setStyleSheet("background: #222222;color: #BBBBBB;font - family: 'Segoe UI'; ");
}


/// <summary>
/// 绘制事件（窗口激活时才自动触发一次）
/// </summary>
/// <param name="qPaintEvent"></param>
void LED::paintEvent(QPaintEvent* qPaintEvent)
{
	Config* config = g_MainForm->config;

	//
	// 开始在QPixmap画布设备上绘图
	//
	QPainter painter;		 // 初始化绘图对象
	QPixmap pixmap(size());  // 创建一个画布设备（QPixmap），将画布的大小设置为和widget一样
	painter.begin(&pixmap);  // 开始画（调用begin之后绘图对象的画笔、画刷都被重置）
	QString errorInfo = QString::fromLocal8Bit("请联系管理员处理：");
	//
	int 当前LED模组的y坐标 = config->第一个LED模组的y坐标;
	int 当前LED模组的x坐标 = config->第一个LED模组的x坐标;
	for (int i = 0; i < config->LEDcol; i++)
	{
		当前LED模组的y坐标 = config->第一个LED模组的y坐标;
		for (int j = 0; j < config->LEDrow; j++)
		{
			painter.setFont(QFont("宋体", config->fontSize));		//设置字体
			painter.setBrush(Qt::black);							//设置画刷（即区域的填充颜色）
			painter.setPen(QPen(Qt::yellow, 1, Qt::SolidLine));		//设置画笔
			// 绘制矩形
			if (config->isShowRect == true)
				painter.drawRect(QRect(当前LED模组的x坐标, 当前LED模组的y坐标, config->LEDwidth, config->LEDheight));
			// 绘制文字：采血间号
			char* RoomNum;
			QByteArray qbaTemp;
			if ((i * config->LEDrow + j) < 10) {
				RoomNum = 采血间号[i * config->LEDrow + j];	// 带圆圈的符号最多显示到⑩，从第11开始QT不能正常绘制了
			}
			else {
				qbaTemp = QString::fromLocal8Bit("采血%1").arg(i * config->LEDrow + j + 1).toLocal8Bit();
				RoomNum = qbaTemp.data();
			}
			painter.drawText(QPoint(当前LED模组的x坐标 + config->采血文字相对于矩形的x偏移, 当前LED模组的y坐标 + config->文字相对于矩形的y偏移), QString::fromLocal8Bit(RoomNum));

			// 有温馨提示时绘制"温馨提示"，否则绘制"排队号 + 姓名"
			painter.setPen(QPen(Qt::red, 1, Qt::SolidLine));				// 红色画笔
			//painter.setPen(QPen(QColor(255, 0, 255), 1, Qt::SolidLine));	// 洋红色画笔
			QByteArray qbaMsg;
			if (false == g_MainForm->info[i * config->LEDrow + j].温馨提示.isEmpty())
				painter.drawText(QPoint(当前LED模组的x坐标 + config->采血文字相对于矩形的x偏移 + config->姓名文字相对于矩形的x偏移, 当前LED模组的y坐标 + config->文字相对于矩形的y偏移), (g_MainForm->info[i * config->LEDrow + j].温馨提示));
			else if (QString::compare(g_MainForm->info[i * config->LEDrow + j].排队号码, QString("initPDHM")) != 0)
				painter.drawText(QPoint(当前LED模组的x坐标 + config->采血文字相对于矩形的x偏移 + config->姓名文字相对于矩形的x偏移, 当前LED模组的y坐标 + config->文字相对于矩形的y偏移), (g_MainForm->info[i * config->LEDrow + j].排队号码 + " " + g_MainForm->info[i * config->LEDrow + j].加密姓名));
			// 记录坐标（用于在设置温馨提示时定位到LED模组）
			g_MainForm->info[i * config->LEDrow + j].起始点坐标 = QPoint(当前LED模组的x坐标, 当前LED模组的y坐标);
			//
			当前LED模组的y坐标 += config->LEDheight;
		}
		当前LED模组的x坐标 += config->LEDwidth;
	}


	// 绘制：医院LOGO
	QPixmap pix;
	pix.load(":/LED/Icon/logo2.png");
	QPixmap logo_scaledPixmap = pix.scaled(QSize(LOGO_宽, LOGO_高), Qt::KeepAspectRatio);
	painter.drawPixmap(10, 当前LED模组的y坐标 + config->LogoStartY, LOGO_宽, LOGO_高, logo_scaledPixmap);//绘制图片(横坐标、纵坐标、宽度、高度)


	// 绘制：院区标识
	QString hospital;
	if (config->hospitalFlag == 1)
		hospital = QString::fromLocal8Bit("龙岩市第一医院（总院）");
	else if (config->hospitalFlag == 2)
		hospital = QString::fromLocal8Bit("龙岩市第一医院（莲东分院）");
	painter.setFont(QFont("宋体", 16));
	painter.setPen(QPen(Qt::green, 1, Qt::SolidLine));
	painter.drawText(QPoint(LOGO_宽 + 30, 当前LED模组的y坐标 + config->LogoStartY + 40 + 40), hospital + "\t" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));

	// 绘制：剩余未叫号人数
	painter.setFont(QFont("宋体", 16 + 2, QFont::Bold)); //粗体
	painter.drawText(QPoint(LOGO_宽 + 30, 当前LED模组的y坐标 + config->LogoStartY + 40), QString::fromLocal8Bit("剩余 %1 人").arg(this->remainPeople));

	//画完了
	painter.end();

	//
	// 开始在Widget设备上绘图
	//
	painter.begin(this);                //开始画
	painter.drawPixmap(0, 0, pixmap);   //在Widget上绘制pixmap画布图像
	painter.end();                      //画完了
}

/// <summary>
/// 连接Oracle
/// </summary>
/// <returns></returns>
bool LED::Connect()
{

	this->db = QSqlDatabase::addDatabase("QOCI");			//自己编译的QOCI驱动
	db.setHostName(g_MainForm->config->IP);
	db.setDatabaseName(g_MainForm->config->DatabaseName);		//数据库的实例名，而不是数据库名（ 外网 bsoft ; 内网 portal2 ）
	db.setUserName(g_MainForm->config->UserName);
	db.setPassword(g_MainForm->config->Password);
	db.setPort(g_MainForm->config->Port.toInt());
	while (true)
	{
		if (db.open() == true) {
			g_MainForm->m_systemTray->showMessage	//托盘气泡提示
			(
				QString::fromLocal8Bit("提示"),				// 标题
				QString::fromLocal8Bit("Oracle连接成功"),	// 内容
				QIcon(":/LED/Icon/timg.png"),				// 图标
				3 * 1000									// 气泡显示时长
			);
			break;
		}
		else
		{
			//错误提示
			QString str = db.lastError().text() + " (" + g_MainForm->config->IP + ":" + g_MainForm->config->Port + " " + g_MainForm->config->DatabaseName + " )";
			g_MainForm->m_systemTray->showMessage	//托盘气泡提示
			(
				QString::fromLocal8Bit("连接失败（5秒后重试）"),	// 标题
				str,											// 内容
				QIcon(":/LED/Icon/timg.png"),					// 图标
				5 * 1000										// 气泡显示时长
			);
			Sleep(3333);
		}
	}
	return true;
}

/// <summary>
/// 断开Oracle连接
/// </summary>
/// <returns></returns>
bool LED::DisConnect()
{
	this->db.close();         //释放数据库连接
	return true;
}




